package de.mmis.core.tuplespace;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;

/**
 * command queue using tuplespace to store elements and results
 * 
 * @author Christian Koch
 */
public class TuplespaceCommandQueue extends TuplespaceQueue {
	private static final String QUEUE_ELEM_RESULT_NAME = "-queue-elem-result";

	private final LeafNode queueElemResultName;
	private final InnerNode queueElemResultQueryTuple;

	/**
	 * creates new command queue
	 * 
	 * @param space
	 *            tuplespace client connected to used server
	 * @param name
	 *            queue name (combined name and id should be unique)
	 * @param id
	 *            object id queue belongs to (combined name and id should be
	 *            unique)
	 * @throws TuplespaceException
	 *             error while reading or writing to tuplespace server
	 */
	public TuplespaceCommandQueue(Tuplespace space, String name, Tree id)
			throws TuplespaceException {
		this(space, name, id, TuplespaceContainer.DEFAULT_TIMEOUT,
				TuplespaceContainer.DEFAULT_LEASETIME);
	}

	/**
	 * creates new command queue
	 * 
	 * @param space
	 *            tuplespace client connected to used server
	 * @param name
	 *            queue name (combined name and id should be unique)
	 * @param id
	 *            object id queue belongs to (combined name and id should be
	 *            unique)
	 * @param timeout
	 *            default time span to wait trying to take queue tuple
	 * @throws TuplespaceException
	 *             error while reading or writing to tuplespace server
	 */
	public TuplespaceCommandQueue(Tuplespace space, String name,
			Tree id, long timeout) throws TuplespaceException {
		this(space, name, id, timeout, TuplespaceContainer.DEFAULT_LEASETIME);
	}

	/**
	 * creates new command queue
	 * 
	 * @param space
	 *            tuplespace client connected to used server
	 * @param name
	 *            queue name (combined name and id should be unique)
	 * @param id
	 *            object id queue belongs to (combined name and id should be
	 *            unique)
	 * @param leaseTime
	 *            time to live between two consecutive push or pop operations
	 * @param timeout
	 *            default time span to wait trying to take queue tuple
	 * @throws TuplespaceException
	 *             error while reading or writing to tuplespace server
	 */
	public TuplespaceCommandQueue(Tuplespace space, String name,
			Tree id, long timeout, long leaseTime)
			throws TuplespaceException {
		super(space, name, id, timeout, leaseTime);

		this.queueElemResultName = new LeafNode(
				(QUEUE_NAME_PREFIX + name + QUEUE_ELEM_RESULT_NAME));

		this.queueElemResultQueryTuple = new InnerNode(queueElemResultName, id,
				new LeafNode("?"), new LeafNode("?"));
	}

	@Override
	public void deinitQueue(long timeout) throws TuplespaceException {
		super.deinitQueue(timeout);

		while (space.takeIfExists(queueElemResultQueryTuple) != null) {
			// take until empty
		}
	}

	/**
	 * write result of last read command into tuplespace with default leaseTime
	 * 
	 * @param result
	 *            result of last read command
	 * @throws TuplespaceException
	 *             error while reading or writing to tuplespace server
	 */
	public void writeResult(Tree result) throws TuplespaceException {
		writeResult(result, leaseTime);
	}

	/**
	 * write result of last read command into tuplespace with specified
	 * leaseTime
	 * 
	 * @param result
	 *            result of last read command
	 * @param leaseTime
	 *            time to live of written result
	 * @throws TuplespaceException
	 *             error while reading or writing to tuplespace server
	 */
	public void writeResult(Tree result, long leaseTime)
			throws TuplespaceException {
		InnerNode temp = new InnerNode(queueElemResultName, id, new LeafNode(Long
				.toString(getLastReadElem())), result);

		space.write(temp, leaseTime);
	}

	/**
	 * remove and return result of specified command, using default timeout
	 * 
	 * @param index
	 *            queue index of command to get result from (returned by push
	 *            operation)
	 * @return result of specified command
	 * @throws TuplespaceException
	 *             error while reading or writing to tuplespace server
	 * @throws TimeoutException
	 *             timed out while trying to take queue or element tuples
	 */
	public Tree takeResult(long index) throws TuplespaceException,
			TimeoutException {
		return takeResult(index, getTimeout());
	}

	/**
	 * remove and return result of specified command
	 * 
	 * @param index
	 *            queue index of command to get result from (returned by push
	 *            operation)
	 * @param timeout
	 *            time span to wait trying to take result tuple.
	 * @return result of specified command
	 * @throws TuplespaceException
	 *             error while reading or writing to tuplespace server
	 * @throws TimeoutException
	 *             timed out while trying to take queue or element tuples
	 */
	public Tree takeResult(long index, long timeout) throws TuplespaceException, TimeoutException
	{
		InnerNode temp = new InnerNode(queueElemResultName, id, new LeafNode(Long
				.toString(index)), new LeafNode("?"));

		InnerNode result = (InnerNode) space.take(temp, timeout);

		if (result == null)
			throw new TimeoutException("result take timed out");
		return result.getSubTree(3);
	}

	/**
	 * remove and return result of specified command, if it exists.
	 * 
	 * @param index
	 *            queue index of command to get result from (returned by push
	 *            operation)
	 * @return result of specified command, or null, if not present
	 * @throws TuplespaceException
	 *             error while reading or writing to tuplespace server
	 */
	public Tree takeResultIfExists(long index)
			throws TuplespaceException {
		InnerNode temp = new InnerNode(queueElemResultName, id, new LeafNode(Long
				.toString(index)), new LeafNode("?"));

		InnerNode result = (InnerNode) space.takeIfExists(temp);

		if (result == null)
			return null;
		return result.getSubTree(3);
	}

	/**
	 * return (but not remove) result of specified command, using default
	 * timeout
	 * 
	 * @param index
	 *            queue index of command to get result from (returned by push
	 *            operation)
	 * @return result of specified command
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 * @throws TimeoutException
	 *             timed out while trying to take queue or element tuples
	 */
	public Tree readResult(long index) throws IOException,
			TimeoutException {
		return readResult(index, getTimeout());
	}

	/**
	 * return (but not remove) result of specified command
	 * 
	 * @param index
	 *            queue index of command to get result from (returned by push
	 *            operation)
	 * @param timeout
	 *            time span to wait trying to take result tuple
	 * @return result of specified command
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 * @throws TimeoutException
	 *             timed out while trying to take queue or element tuples
	 */
	public Tree readResult(long index, long timeout) throws IOException,
			TimeoutException {
		try {
			InnerNode temp = new InnerNode(queueElemResultName, id, new LeafNode(Long
					.toString(index)), new LeafNode("?"));

			InnerNode result = (InnerNode) space.read(temp, timeout);

			if (result == null)
				throw new TimeoutException("result read timed out");
			return result.getSubTree(3);

		} catch (TuplespaceException e) {
			throw new IOException("Tuplespace error", e);
		}
	}

	/**
	 * (non-blocking) return (but not remove) result of specified command, if it
	 * exists
	 * 
	 * @param index
	 *            queue index of command to get result from (returned by push
	 *            operation)
	 * @return result of specified command, or null, if not present
	 * @throws IOException
	 *             error while reading or writing to tuplespace server
	 */
	public Tree readResultIfExists(long index) throws IOException {
		try {
			InnerNode temp = new InnerNode(queueElemResultName, id, new LeafNode(Long
					.toString(index)), new LeafNode("?"));

			InnerNode result = (InnerNode) space.readIfExists(temp);

			if (result == null)
				return null;
			return result.getSubTree(3);

		} catch (TuplespaceException e) {
			throw new IOException("Tuplespace error", e);
		}
	}
}
